﻿#include "CSpeechAlert.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QDir>
#include <QTextStream>
#include <QDebug>

CSpeechAlert::CSpeechAlertTrush CSpeechAlert::uTrush;
CSpeechAlert* CSpeechAlert::m_pInstance = nullptr;            //单例类
QMutex CSpeechAlert::m_locker;

CSpeechAlert::CSpeechAlert()
{
	m_bIsInit = false;
}

CSpeechAlert::~CSpeechAlert()
{
	StopWorkThread();
}

/**
* @brief getInstance 获取单例
* @param
*/
CSpeechAlert * CSpeechAlert::getInstance()
{
	if (nullptr == m_pInstance)
	{
		m_locker.lock();
		if (nullptr == m_pInstance)
		{
			CSpeechAlert* pInstance = new CSpeechAlert();
			m_pInstance = pInstance;
			m_pInstance->initData();
			m_pInstance->StartWorkThread();
		}
		m_locker.unlock();
	}

	return m_pInstance;
}

/**
* @brief getEngine 获取可用引擎名称
* @param
*/
QStringList CSpeechAlert::getAvailableEngines()
{
	return QTextToSpeech::availableEngines();
}

/**
* @brief getAvailableLocales 获取可用语言
* @param
*/
QStringList CSpeechAlert::getAvailableLocales()
{
	return m_mapLocales.keys();
}

/**
* @brief getAvailableVoice 获取可用声音
* @param
*/
QStringList CSpeechAlert::getAvailableVoice()
{
	return m_mapVoice.keys();
}

/**
* @brief getEngine 设置引擎
* @param sEngineName 引擎名称
*/
bool CSpeechAlert::setEngine(const QString &sEngineName)
{
	if (nullptr == m_pSpeech)
	{
		return false;
	}

	//存在该引擎
	if (m_mapEngines.contains(sEngineName))
	{
		if (nullptr != m_pSpeech)
		{
			delete m_pSpeech;
			m_pSpeech = nullptr;
		}

		m_pSpeech = new QTextToSpeech(sEngineName);
		connect(m_pSpeech, &QTextToSpeech::stateChanged, this, &CSpeechAlert::slotStateChanged, Qt::UniqueConnection);
		//重新加载语言
		loadLocales();
		//重新加载语音
		loadVoice();

		return true;
	}

	return false;
}

/**
* @brief setLocale 设置语言
* @param qLocale 语言
*/
bool CSpeechAlert::setLocale(const QString &sLocaleName)
{
	if (nullptr == m_pSpeech)
	{
		return false;
	}

	//存在该语言
	if (m_mapLocales.contains(sLocaleName))
	{
		QLocale qLocale = m_mapLocales.value(sLocaleName);
		m_pSpeech->setLocale(qLocale);
		return true;
	}

	return false;
}

/**
* @brief setVoice 设置声音
* @param sVoiceName 声音名称
*/
bool CSpeechAlert::setVoice(const QString &sVoiceName)
{
	if (nullptr == m_pSpeech)
	{
		return false;
	}

	//存在该语声音
	if (m_mapVoice.contains(sVoiceName))
	{
		QVoice qVoice = m_mapVoice.value(sVoiceName);
		m_pSpeech->setVoice(qVoice);
		return true;
	}

	return false;
}

/**
* @brief slotSpeechFinished 播报结束
* @param
*/
void CSpeechAlert::slotSpeechFinished()
{
	m_qSpeechLoop.quit();
}

/**
* @brief initData 初始化数据
* @param
*/
void CSpeechAlert::initData()
{
	//加载引擎
	loadEngines();
	//使用默认引擎
	m_pSpeech = new QTextToSpeech(this);
	connect(m_pSpeech, &QTextToSpeech::stateChanged, this, &CSpeechAlert::slotStateChanged, Qt::UniqueConnection);
	//加载语言
	loadLocales();
	//加载语音
	loadVoice();

	/*线程相关*/
	m_nReConnectSplitNum = 0;
	m_nReConnectSplitTime = 500;
	m_bIsInit = true;

	/*播报相关*/
	m_bSpeechPause = false;
	m_bIsSpeak = false;
	connect(m_pInstance, &CSpeechAlert::sigSpeechFinished, m_pInstance, &CSpeechAlert::slotSpeechFinished, Qt::UniqueConnection);

	/*播报时间限制相关*/
	m_nTimerType = -1;
	m_nTimeSplit = 100;
	m_nCurrLimitTime = 0;

	/*文件记录模块*/
	m_bOpenFileRecord = true;
}

/**
* @brief loadEngine 加载引擎
* @param
*/
void CSpeechAlert::loadEngines()
{
	//获取可用引擎
	QStringList listEngineName = QTextToSpeech::availableEngines();
	for (int i = 0; i < listEngineName.size(); i++)
	{
		m_mapEngines.insert(listEngineName[i], listEngineName[i]);
	}
}

/**
* @brief loadVoice 加载声音
* @param
*/
void CSpeechAlert::loadLocales()
{
	//先清空
	m_mapLocales.clear();

	QVector<QLocale> veclocales = m_pSpeech->availableLocales();
	for (int i = 0;i < veclocales.size();i++)
	{
		QLocale qLocale = veclocales[i];
		m_mapLocales.insert(qLocale.name(), qLocale);
	}
}

/**
* @brief loadVoice 加载声音
* @param
*/
void CSpeechAlert::loadVoice()
{
	//先清空
	m_mapVoice.clear();

	QVector<QVoice> vecVoice = m_pSpeech->availableVoices();
	for (int i = 0; i < vecVoice.size(); i++)
	{
		QVoice qVoice = vecVoice[i];
		m_mapVoice.insert(qVoice.name(), qVoice);
	}
}

/**
* @brief handleSpeech 处理播报
* @param
*/
void CSpeechAlert::handleSpeech()
{
	if (nullptr == m_pSpeech)
	{
		return ;
	}

	//暂停
	if (m_bSpeechPause)
	{
		return;
	}

	//无任务
	if (0 == m_queueSpeech.size())
	{
		return;
	}

	//取头部队列
	m_locker.lock();
	QString sCurrId = m_queueSpeech.dequeue();
	//判断是否存在
	if (!m_mapSpeechData.contains(sCurrId))
	{
		m_locker.unlock();
		return;
	}
	//获取播报配置
	SSpeechSet stSpeech = m_mapSpeechData.value(sCurrId);

	//日志记录
	QString sType = QString::fromLocal8Bit("固定次数");
	if (enFixed == stSpeech.enType)
	{
		sType = QString::fromLocal8Bit("固定次数");
	}
	else if(enLoop == stSpeech.enType)
	{
		sType = QString::fromLocal8Bit("循环");
	}
	writeToFile(QString::fromLocal8Bit("\n识别到内容: ") + stSpeech.sContent
		+ QString::fromLocal8Bit("\n播报类型: ") + sType
		+ QString::fromLocal8Bit("\n播报次数: ") + QString::number(stSpeech.nFixedNum)
		+ QString::fromLocal8Bit("\n时间限制: ") + QString::number(stSpeech.nSpeechTimeLimit));

	//处理播报次数问题
	switch (stSpeech.enType)
	{
	case enFixed:   //固定值 判断次数是否降为0 为0时不再插入头部
		//播报次数减1
		stSpeech.nFixedNum--;
		m_mapSpeechData.insert(stSpeech.sId, stSpeech);

		if (stSpeech.nFixedNum != 0)
		{
			m_queueSpeech.prepend(sCurrId);
		}
		//播报次数结束了 移除
		else
		{
			m_mapSpeechData.remove(sCurrId);
		}
		break;
	case enLoop:    //循环 无限制往头部插入 直至移除
		m_queueSpeech.prepend(sCurrId);
		break;
	default:
		break;
	}
	m_locker.unlock();

	//播放语音(委托给主线程)
	QMetaObject::invokeMethod(m_pSpeech,
		"say",
		Qt::QueuedConnection,
		Q_ARG(QString, stSpeech.sContent));

	//假设设置了时间限制，开启定时监测，按时间限制结束播报
	if (0 != stSpeech.nSpeechTimeLimit)
	{
		m_nCurrLimitTime = stSpeech.nSpeechTimeLimit;
		m_nTimerType = startTimer(m_nTimeSplit); // 启动定时器
		m_qSpeechTime.restart();
	}
	else
	{
		m_nCurrLimitTime = 0;
		killTimer(m_nTimerType); // 停止定时器
	}

	//播放时间延时
	m_qSpeechLoop.exec();//延时指定毫秒

	//日志记录
	writeToFile(QString::fromLocal8Bit("内容: ") + stSpeech.sContent + QString::fromLocal8Bit(" 播报结束"));

	//时间到 无论是否说完 直接停止
	m_pSpeech->stop();

	//日志记录
	writeToFile(QString::fromLocal8Bit("播报队列剩余: ") + QString::number(m_queueSpeech.size())
		+ QString::fromLocal8Bit(" 播报数据剩余: ") + QString::number(m_mapSpeechData.size()) + "\n");
}

/**
* @brief writeToFile 将日志写入到文件
* @param
*/
void CSpeechAlert::writeToFile(const QString & sLog)
{
	if (false == m_bOpenFileRecord)
	{
		return;
	}

	QString sCurrentPath = QCoreApplication::applicationDirPath();     //根目录
	QDateTime qCurrDateTime = QDateTime::currentDateTime();
	QString sYear = qCurrDateTime.toString("yyyy");
	QString sMonth = qCurrDateTime.toString("MMdd");
	QString sTime = qCurrDateTime.toString("hhmmss");

	/*创建各类型报表保存路径*/
	//创建料场报表保存地址
	QString sReportUrl = sCurrentPath + "/SpeechAlertLog/" + sYear + "/" + sMonth;
	QDir dirReport(sReportUrl);
	if (!dirReport.exists())
	{
		if (dirReport.mkpath("."));
	}

	// 设置文件路径（文件名为 "output.txt"）
	QString filePath = sReportUrl + "/speechLog.txt";

	// 打开文件
	QFile file(filePath);
	if (file.open(QIODevice::Append | QIODevice::Text)) 
	{
		QTextStream out(&file);
		out.setCodec("UTF-8");  // 设置编码格式为 UTF-8
		out << qCurrDateTime.toString("yyyy-MM-dd hh:mm:ss") + " " + sLog << "\n";  // 写入数据并换行
		file.close();
	}
}

/**
* @brief setRate 设置语速
* @param nRate 语速   1.0为默认语速，小于1.0会使语音播放变慢，大于1.0则会使播放变快。
*/
void CSpeechAlert::setRate(float fRate)
{
	if (nullptr == m_pSpeech)
	{
		return;
	}

	m_pSpeech->setRate(fRate);
}

/**
* @brief setPitch 设置音调
* @param nPitch 音调   1.0为默认音调，小于1.0会使声音变低，大于1.0会使声音变高。
*/
void CSpeechAlert::setPitch(float fPitch)
{
	if (nullptr == m_pSpeech)
	{
		return;
	}

	m_pSpeech->setPitch(fPitch);
}

/**
* @brief setVolume 设置音量
* @param nVolume 音量   0.0 - 1.0
*/
void CSpeechAlert::setVolume(float fVolume)
{
	if (nullptr == m_pSpeech)
	{
		return;
	}

	if (fVolume > 1.0)
	{
		fVolume = 1.0;
	}

	if (fVolume < 0.0)
	{
		fVolume = 0.0;
	}

	m_pSpeech->setVolume(fVolume);
}

/**
* @brief setPause 暂停
* @param bPause 是否暂停
*/
void CSpeechAlert::setPause(bool bPause)
{
	if (nullptr == m_pSpeech)
	{
		return;
	}

	//请求暂停
	if (bPause)
	{
		//结束当前正在进行的播报
		m_qSpeechLoop.quit();
		m_pSpeech->stop();
		m_bSpeechPause = true;
	}
	//恢复
	else
	{
		m_bSpeechPause = false;
	}
}

/**
* @brief speech 播报 添加到队列 按顺序播报
* @param stSpeech 播报配置
*/
QString CSpeechAlert::speech(SSpeechSet stSpeech)
{
	if (nullptr == m_pSpeech)
	{
		return "";
	}

	//内容为空 不插入
	if (stSpeech.sContent.isEmpty())
	{
		return "";
	}

	QString sId = stSpeech.sId;
	//插入到尾部 顺序执行
	m_locker.lock();
	m_queueSpeech.enqueue(sId);
	m_mapSpeechData.insert(sId, stSpeech);
	m_locker.unlock();

	return sId;
}

/**
* @brief speechNow 立刻播报
* @param stSpeech 播报配置
*/
QString CSpeechAlert::speechNow(SSpeechSet stSpeech)
{
	if (nullptr == m_pSpeech)
	{
		return "";
	}

	//内容为空 不插入
	if (stSpeech.sContent.isEmpty())
	{
		return "";
	}

	QString sId = stSpeech.sId;

	//结束当前正在进行的播报
	m_bSpeechPause = true;
	m_qSpeechLoop.quit();
	m_pSpeech->stop();

	//插入到头部 立即执行
	m_locker.lock();
	m_queueSpeech.prepend(sId);
	m_mapSpeechData.insert(sId, stSpeech);
	m_locker.unlock();
	m_bSpeechPause = false;

	return sId;
}

/**
* @brief removeByContent 通过内容标识移除播报
* @param sContent 内容
*/
void CSpeechAlert::removeByContent(const QString & sContent)
{
	m_locker.lock();
	for (int i = 0;i < m_queueSpeech.size();i++)
	{
		QString sId = m_queueSpeech.at(i);
		SSpeechSet stSet = m_mapSpeechData.value(sId);
		if (stSet.sContent == sContent)
		{
			//移出队列
			m_queueSpeech.removeOne(sId);
			//移出数据
			m_mapSpeechData.remove(sId);
		}
	}
	m_locker.unlock();
}

/**
* @brief removeById 通过生成的Id标识移除播报
* @param sId 插入时生成的Id
*/
void CSpeechAlert::removeById(const QString & sId)
{
	m_locker.lock();
	for (int i = 0; i < m_queueSpeech.size(); i++)
	{
		QString sCurrId = m_queueSpeech.at(i);
		if (sCurrId == sId)
		{
			//移出队列
			m_queueSpeech.removeOne(sId);
			//移出数据
			m_mapSpeechData.remove(sId);
		}
	}
	m_locker.unlock();
}

/**
* @brief clear 清空所有队列
* @param
*/
void CSpeechAlert::removeAll()
{
	if (nullptr == m_pSpeech)
	{
		return;
	}

	//结束当前正在进行的播报
	m_bSpeechPause = true;
	m_qSpeechLoop.quit();
	m_pSpeech->stop();

	//清空队列
	m_locker.lock();
	m_queueSpeech.clear();
	m_mapSpeechData.clear();
	m_locker.unlock();
	m_bSpeechPause = false;
}

/**
* @brief openLog 开启日志
* @param
*/
void CSpeechAlert::openLog(bool bOpen)
{
	m_bOpenFileRecord = bOpen;
}

//定时器 监测是否按时限中断播报
void CSpeechAlert::timerEvent(QTimerEvent * event)
{
	if (event->timerId() == m_nTimerType)
	{
		int nCurrTime = m_qSpeechTime.elapsed();
		//判断播放状态 且 时间到达限制
		if (true == m_bIsSpeak && nCurrTime >= m_nCurrLimitTime)
		{
			m_qSpeechLoop.quit();

			killTimer(m_nTimerType); // 停止定时器
		}
	}
}

/**
* @brief slotWorkProc 工作线程
* @param
*/
void CSpeechAlert::slotWorkProc()
{
	while (false == m_bStopWorkThread)
	{
		if (true == m_bStopWorkThread)
		{
			break;
		}

		//初始化未完成
		if (false == m_bIsInit)
		{
			return;
		}

		/*时间判断 防止长时间线程退不出去*/
		int nTotalNum = m_nReConnectSplitTime / 500;

		QEventLoop loop;
		QTimer::singleShot(500, &loop, SLOT(quit()));
		loop.exec();//延时500毫秒

		if (m_nReConnectSplitNum < nTotalNum)
		{
			m_nReConnectSplitNum++;
			continue;
		}
		else
		{
			m_nReConnectSplitNum = 1;
		}

		//声音处理
		handleSpeech();
	}
}

/**
* @brief slotStateChanged 状态改变
* @param
*/
void CSpeechAlert::slotStateChanged(QTextToSpeech::State state)
{
	//播报就绪
	if (QTextToSpeech::State::Ready == state)
	{
		//正在播报
		if (true == m_bIsSpeak)
		{
			m_bIsSpeak = false;
			m_pInstance->sigSpeechFinished();
		}
	}
	//播报开始
	else if(QTextToSpeech::State::Speaking == state)
	{
		//没有播报
		if (false == m_bIsSpeak)
		{
			m_bIsSpeak = true;
		}
	}
}